// The libMesh Finite Element Library.
// Copyright (C) 2002-2019 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA



// <h1>Introduction Example 5 - Run-Time Quadrature Rule Selection</h1>
// \author Benjamin S. Kirk
// \date 2003
//
// This is the fifth example program.  It builds on
// the previous two examples, and extends the use
// of the std::unique_ptr as a convenient build method to
// determine the quadrature rule at run time.


// C++ include files that we need
#include <iostream>
#include <sstream>
#include <algorithm>
#include <math.h>

// Basic include file needed for the mesh functionality.
#include "libmesh/libmesh.h"
#include "libmesh/mesh.h"
#include "libmesh/mesh_generation.h"
#include "libmesh/exodusII_io.h"
#include "libmesh/linear_implicit_system.h"
#include "libmesh/equation_systems.h"

// Define the Finite Element object.
#include "libmesh/fe.h"

// Define the base quadrature class, with which
// specialized quadrature rules will be built.
#include "libmesh/quadrature.h"

// Define useful datatypes for finite element
// matrix and vector components.
#include "libmesh/sparse_matrix.h"
#include "libmesh/numeric_vector.h"
#include "libmesh/dense_matrix.h"
#include "libmesh/dense_vector.h"

// Define the DofMap, which handles degree of freedom
// indexing.
#include "libmesh/dof_map.h"

// To impose Dirichlet boundary conditions
#include "libmesh/dirichlet_boundaries.h"
#include "libmesh/analytic_function.h"

// The definition of a geometric element
#include "libmesh/elem.h"
#include "libmesh/enum_solver_package.h"
#include "libmesh/enum_quadrature_type.h"

#include "libmesh/enum_solver_type.h"
#include "libmesh/enum_preconditioner_type.h"
#include "libmesh/linear_solver.h"
// Bring in everything from the libMesh namespace
using namespace libMesh;


#include "EbESystem.h"
#include "AssemblyWorklet.h"


using SystemBase = EbESystem;
//using SystemBase = LinearImplicitSystem;

// Function prototype, as before.
void assemble_poisson(EquationSystems & es,
                      const std::string & system_name);


void exact_solution_wrapper (DenseVector<Number> & output,
                             const Point & p,
                             const Real)
{
    //output(0) = exact_solution(p(0), p(1), p(2));
    Real x = p(0);
    if (x <= 0)
        output(0) = 1;
    else
        output(0) = 2;
}


// The quadrature type the user requests.

void assemble_vtkm(EquationSystems& es,
    const std::string& system_name)
{
    Run(es);
}

// Begin the main program.
int main (int argc, char ** argv)
{
  LibMeshInit init (argc, argv);

  libmesh_example_requires(libMesh::default_solver_package() != INVALID_SOLVER_PACKAGE,
                           "--enable-petsc, --enable-trilinos, or --enable-eigen");

 

  libmesh_example_requires(3 <= LIBMESH_DIM, "3D support");

  Mesh mesh(init.comm());
  MeshTools::Generation::build_square(mesh,
      10, 10,
      0, 1,
      0, 1,
      QUAD4);

  EquationSystems equation_systems (mesh);

  //equation_systems.add_system<LinearImplicitSystem> ("Poisson");
  equation_systems.add_system<SystemBase> ("Poisson");

  unsigned int u_var = equation_systems.get_system("Poisson").add_variable("u", FIRST);

  //equation_systems.get_system("Poisson").attach_assemble_function(assemble_poisson);
  equation_systems.get_system("Poisson").attach_assemble_function (assemble_vtkm);

  std::set<boundary_id_type> boundary_ids;
  // the dim==1 mesh has two boundaries with IDs 0 and 1
  //boundary_ids.insert(0);
  boundary_ids.insert(1);
  //boundary_ids.insert(2);
  boundary_ids.insert(3);
  //boundary_ids.insert(4);
  //boundary_ids.insert(5);

  std::vector<unsigned int> variables(1);
  variables[0] = u_var;

  AnalyticFunction<> exact_solution_object(exact_solution_wrapper);

  DirichletBoundary dirichlet_bc
    (boundary_ids, variables, exact_solution_object);

  equation_systems.get_system("Poisson").get_dof_map().add_dirichlet_boundary(dirichlet_bc);

  equation_systems.init();
  equation_systems.print_info();
  equation_systems.get_system("Poisson").solve();

  std::ostringstream f_name;
  f_name << "out_0" << ".e";

#ifdef LIBMESH_HAVE_EXODUS_API
  ExodusII_IO(mesh).write_equation_systems (f_name.str(),
                                            equation_systems);
#endif // #ifdef LIBMESH_HAVE_EXODUS_API

  // All done.
  return 0;
}




//void assemble_poisson(EquationSystems & es,
//                      const std::string & libmesh_dbg_var(system_name))
//{
//  libmesh_assert_equal_to (system_name, "Poisson");
//
//  const MeshBase & mesh = es.get_mesh();
//
//  const unsigned int dim = mesh.mesh_dimension();
//
//  //LinearImplicitSystem& system = es.get_system<LinearImplicitSystem>("Poisson");
//  auto & system = es.get_system<SystemBase>("Poisson");
// 
//
//  const DofMap & dof_map = system.get_dof_map();
//
//  FEType fe_type = dof_map.variable_type(0);
//
//  // Build a Finite Element object of the specified type.  Since the
//  // FEBase::build() member dynamically creates memory we will
//  // store the object as a std::unique_ptr<FEBase>.  Below, the
//  // functionality of std::unique_ptr's is described more detailed in
//  // the context of building quadrature rules.
//  std::unique_ptr<FEBase> fe (FEBase::build(dim, fe_type));
//
//  // Now this deviates from example 4.  we create a
//  // 5th order quadrature rule of user-specified type
//  // for numerical integration.  Note that not all
//  // quadrature rules support this order.
//  std::unique_ptr<QBase> qrule(QBase::build(quad_type, dim, THIRD));
//
//  // Tell the finite element object to use our
//  // quadrature rule.  Note that a std::unique_ptr<QBase> returns
//  // a QBase* pointer to the object it handles with get().
//  // However, using get(), the std::unique_ptr<QBase> qrule is
//  // still in charge of this pointer. I.e., when qrule goes
//  // out of scope, it will safely delete the QBase object it
//  // points to.  This behavior may be overridden using
//  // std::unique_ptr<Xyz>::release(), but is currently not
//  // recommended.
//  fe->attach_quadrature_rule (qrule.get());
//
//  // Declare a special finite element object for
//  // boundary integration.
//  std::unique_ptr<FEBase> fe_face (FEBase::build(dim, fe_type));
//
//  // As already seen in example 3, boundary integration
//  // requires a quadrature rule.  Here, however,
//  // we use the more convenient way of building this
//  // rule at run-time using quad_type.  Note that one
//  // could also have initialized the face quadrature rules
//  // with the type directly determined from qrule, namely
//  // through:
//  // \verbatim
//  // std::unique_ptr<QBase>  qface (QBase::build(qrule->type(),
//  // dim-1,
//  // THIRD));
//  // \endverbatim
//  // And again: using the std::unique_ptr<QBase> relaxes
//  // the need to delete the object afterward,
//  // they clean up themselves.
//  std::unique_ptr<QBase>  qface (QBase::build(quad_type,
//                                              dim-1,
//                                              THIRD));
//
//  // Tell the finite element object to use our
//  // quadrature rule.  Note that a std::unique_ptr<QBase> returns
//  // a QBase* pointer to the object it handles with get().
//  // However, using get(), the std::unique_ptr<QBase> qface is
//  // still in charge of this pointer. I.e., when qface goes
//  // out of scope, it will safely delete the QBase object it
//  // points to.  This behavior may be overridden using
//  // std::unique_ptr<Xyz>::release(), but is not recommended.
//  fe_face->attach_quadrature_rule (qface.get());
//
//  // This is again identical to example 4, and not commented.
//  const std::vector<Real> & JxW = fe->get_JxW();
//
//  const std::vector<Point> & q_point = fe->get_xyz();
//
//  const std::vector<std::vector<Real>> & phi = fe->get_phi();
//
//  const std::vector<std::vector<RealGradient>> & dphi = fe->get_dphi();
//
//  DenseMatrix<Number> Ke;
//  DenseVector<Number> Fe;
//  std::vector<dof_id_type> dof_indices;
//
//  // Now we will loop over all the elements in the mesh.
//  // See example 3 for details.
//  for (const auto & elem : mesh.active_local_element_ptr_range())
//    {
//      dof_map.dof_indices (elem, dof_indices);
//
//      const unsigned int n_dofs =
//        cast_int<unsigned int>(dof_indices.size());
//
//      fe->reinit (elem);
//
//      libmesh_assert_equal_to (n_dofs, phi.size());
//
//      Ke.resize (n_dofs, n_dofs);
//
//      Fe.resize (n_dofs);
//
//      // Now loop over the quadrature points.  This handles
//      // the numeric integration.  Note the slightly different
//      // access to the QBase members!
//      for (unsigned int qp=0; qp<qrule->n_points(); qp++)
//        {
//          // Add the matrix contribution
//          for (unsigned int i=0; i != n_dofs; i++)
//            for (unsigned int j=0; j != n_dofs; j++)
//              Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]);
//
//          const Real x = q_point[qp](0);
//          const Real y = q_point[qp](1);
//          const Real z = q_point[qp](2);
//          const Real eps = 1.e-3;
//
//          const Real uxx = (exact_solution(x-eps, y, z) +
//                            exact_solution(x+eps, y, z) +
//                            -2.*exact_solution(x, y, z))/eps/eps;
//
//          const Real uyy = (exact_solution(x, y-eps, z) +
//                            exact_solution(x, y+eps, z) +
//                            -2.*exact_solution(x, y, z))/eps/eps;
//
//          const Real uzz = (exact_solution(x, y, z-eps) +
//                            exact_solution(x, y, z+eps) +
//                            -2.*exact_solution(x, y, z))/eps/eps;
//
//          const Real fxy = 0;// -(uxx + uyy + ((dim == 2) ? 0. : uzz));
//
//
//          // Add the RHS contribution
//          for (unsigned int i=0; i != n_dofs; i++)
//            Fe(i) += JxW[qp]*fxy*phi[i][qp];
//        }
//
//      // If this assembly program were to be used on an adaptive mesh,
//      // we would have to apply any hanging node constraint equations
//      // Call heterogenously_constrain_element_matrix_and_vector to impose
//      // non-homogeneous Dirichlet BCs
//      dof_map.heterogenously_constrain_element_matrix_and_vector(Ke, Fe, dof_indices);
//      //dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices);
//      // The element matrix and right-hand-side are now built
//      // for this element.  Add them to the global matrix and
//      // right-hand-side vector.  The SparseMatrix::add_matrix()
//      // and NumericVector::add_vector() members do this for us.
//      system.matrix->add_matrix (Ke, dof_indices);
//      system.rhs->add_vector    (Fe, dof_indices);
//
//      system.add_matrix(Ke);                                  
//      system.add_rhs(Fe);
//
//    } // end of element loop
//}
